
import

	tango.core.Memory,
	tango.core.Array ,
	tango.core.Thread ,
	tango.core.Traits,
	
	tango.net.Uri,
	tango.math.random.Random,
	tango.net.device.Berkeley,
	tango.net.InternetAddress,
	
	tango.stdc.stringz,
	Int	= tango.text.convert.Integer ,
	
	tango.io.Stdout ,
	tango.io.device.Array ,
	tango.io.device.File ,
	
	tango.time.Clock,
	tango.time.Time,
	libeventd.Types ;
	
static const proxy_host	= "www.baidu.com\0" ;

static char[] tozString(char[] from) {
	char[] to = new char[ from.length + 1];
	memcpy(&to[0], &from[0], from.length );
	to[$-1]	= 0 ;
	return to ;
}

extern(C){
	int evhttp_connection_reset(evhttp_connection*);
	int evhttp_make_header(evhttp_connection*,evhttp_request*);
	int evhttp_write_buffer(evhttp_connection*, void*,void*);
	int evhttp_start_read(evhttp_connection*);
	char* event_mm_strdup_(char*) ;
	int bufferevent_unsuspend_write(bufferevent*,int);
	int bufferevent_unsuspend_read(bufferevent*,int);
	evthread_lock_callbacks* get_evthread_lock_fns() ;
	int _bufferevent_decref_and_unlock(bufferevent*);
	int _bufferevent_incref_and_lock(bufferevent*);
	void evhttp_connection_cb(bufferevent *bufev, short what, void *arg);
	void evhttp_request_dispatch(evhttp_connection*);
}

T RevertByte(T)(T v) {
	static assert(  isIntegerType!(T) );
	T _v		= v;
	T*	p	=  cast(T*) &( (cast(ubyte*) &_v)[ 0 .. T.sizeof ] .reverse )[0] ;
	return	*p ;
}

static bool isNull(T)(T _dg ) {
	static assert( T.sizeof is 8 );
	if( _dg is null || _dg.ptr is null|| _dg.funcptr is null ){
		return true ;
	}
	int ptr	= cast(int) _dg.ptr  ;
	if( ptr is 0 ){
		return true ;
	}
	int fptr	= cast(int) _dg.funcptr  ;
	if( fptr is 0 ){
		return true ;
	}
	return false ;
}

void TAILQ_INSERT_TAIL(char[] field, H, E )(H head, E elm){
	mixin("elm." ~ field ~ ".tqe_next	= null ; ") ;
	mixin("elm." ~ field ~ ".tqe_prev 	= head.tqh_last ; ") ;
	*head.tqh_last	= elm ;
	mixin("head.tqh_last	= &elm." ~ field ~ ".tqe_next; ") ;
}


void main(){

	WSADATA wd = void;
	WSAStartup (0x0202, &wd);
	auto base	= event_base_new();
	evthread_use_windows_threads();
	event_base_start_iocp(base);
	
	Dns.Init(base);
	
	Http* http	= Http.Init(base) ;
	http.bind("127.0.0.1", 8080);
	
	timeval tv	= {99999999,0} ;
	event_base_loopexit(base, &tv);
	
	event_base_dispatch(base);
}

struct Dns {
	alias Dns* 			This ;
	static event_base*	_base ;
	static evdns_base*	_dns ;
	
	static void Init(event_base* base ) {
		assert(base !is null ) ;
		_base	= base ;
		_dns		= evdns_base_new(_base , 0 );
		assert(_dns !is null ) ;
		// evdns_set_log_fn(&logfn);
		int ret	= evdns_base_config_windows_nameservers(_dns);
		assert(ret is 0) ;
	}
	
	static extern(C)	void logfn(int is_warn,  char *msg) {
		Stdout.formatln("{},{}", is_warn, fromStringz(msg) );
	}
	
	static extern(C)	void Server_CallBack(int result, char type, int count, int ttl, void *addresses, void *ctx) {
		This _self	= cast(This) ctx ;
		_self._ts	= Clock.now.unix.seconds ;
		_self._ret	= result ;
		_self._stat	= 2 ; 
		if( _self._ret is 0 ) {
			_self._ttl		= ttl ;
			_self._ip		= new ubyte[ count *  addresses.sizeof  ] ;
			memcpy(&_self._ip[0], addresses,   _self._ip.length  ) ;
		}
		assert( _self._dg !is null ) ;
		while( _self._dg.length > 0 ) {
			auto _dg	= _self._dg[0] ; 
			assert(_dg !is null  && _dg.ptr !is null && _dg.funcptr !is null);
			_dg( _self );
			_self._dg	= _self._dg[ 1 .. $];
		}
	}
	
	static This[]	list = [] ;
	
	alias void delegate(This) Dg_Type ;
	char[]	_dn	;
	Dg_Type	_dg[] ;
	int		_stat	;
	int		_ret	;
	long		_ts	;
	int		_ttl	;
	ubyte[]	_ip	;
	
	
	static void Query(char[] dn , Dg_Type dg) {
		This ptr	= null ;
		foreach( _dns; list) {
			if( dn == _dns._dn[0..$-1] ) {
				ptr	= _dns ;
				break ;
			}
		}
		ubyte[4] ipv ;
		bool	is_ip	= isIp( dn, ipv );
		if(  ptr is null ) {
			ptr		= new Dns ;
			ptr._dn	= new char[ dn.length + 1 ] ;
			memcpy(&(ptr._dn)[0], &dn[0], dn.length );
			ptr._dn[$-1]	= 0 ;
			list		~= ptr ;
			if( is_ip ) {
				ptr._ip	= ipv.dup ;
			}
		}
		long ts	=  Clock.now.unix.seconds ;
		if( is_ip || ts - ptr._ts <= ptr._ttl ) {
			dg(ptr);
		} else {
			ptr._dg	~= dg ;
			if( ptr._stat !is 1 ){
				ptr._stat	= 1 ;
				evdns_base_resolve_ipv4(_dns, ptr._dn.ptr , 0, &Server_CallBack, ptr);
			}
		}
	}
	
	static bool isIp(char[] ip, ubyte[4] ipv) {
		static bool CheckNumber(char[] ip , ref int i , ref ubyte ipv ){
			int j	= i ;
			for( ; j < ip.length ; j ++) {
				if( ip[j] < '0' || ip[j] > '9'  ) {
					break;
				}
			}
			int l	= j - i;
			if( l > 3 ){
				return false ;
			}
			int v	= 0 ;
			for ( int k = 0 ; k  < l; k++) {
				ubyte _v	= ip[ i + l - k -1 ] - '0' ;
				if( k is 0 ){
					v	+= _v ;
				}else if( k is 1 ){
					v	+= _v * 10 ;
				}else if( k is 2 ){
					v	+= _v * 100 ;
				}
			}
			if( v > 255 ){
				return false ;
			}
			ipv	= v ; 
			i	= j ;
			return  true ;
		}
		int i	=  0 ;
		if( !CheckNumber(ip, i, ipv[0] )  || i >=ip.length || ip[i++] !is '.'  ){
			return false ;
		}
		if( !CheckNumber(ip, i, ipv[1] )  || i >=ip.length || ip[i++] !is '.'  ){
			return false ;
		}
		if( !CheckNumber(ip, i, ipv[2] )  || i >=ip.length || ip[i++] !is '.'  ){
			return false ;
		}
		if( !CheckNumber(ip, i, ipv[3] ) ){
			return false ;
		}
		return ip.length is i  ;
	}
}

struct Http {
	event_base*	_base ;
	evhttp*		_http ;
	
	static Http* Init(event_base* base) {
		Http* p	= new Http;
		p._base	= base ;
		p._http	= evhttp_new(base);
		evhttp_set_gencb(p._http, &Http_Gen_Cb, p);
		return p;
	}
	
	bool bind(char[] ip, ushort port) {
		int ret	= evhttp_bind_socket(_http, toStringz(ip) , port) ;
		if( ret is 0 ){
			Stdout.formatln("http://{}:{}/", ip, port).flush ;
			return true;
		}
		Stdout.formatln("bind {}:{} failure", ip, port).flush ;
		return false;
	}
	
	void free() {
		if( _http !is null ) {
			evhttp_free(_http);
			_http	= null ;
		}
	}
	
	static extern(C) void Http_Gen_Cb(evhttp_request* req, void* ctx) {
		Http* _this	= cast(Http*) ctx ;
		auto v	= SReq.Init(_this, req) ;
		v.service ;
	}
}


template IRequest(T) {
	Http*			_http	;
	evhttp_request*		_req	;
	evhttp_connection*	_conn ;
	char[]			_url ;
	
	static if( isReferenceType!(T) ){
		alias T	This ;
	}else{
		alias T*	This ;
	}
	
	This http(Http* http) {
		this._http	= http ;
		return this ;
	}
	
	char[] url() {
		if( _url !is null && _url.length > 0 ) {
			return _url[0..$-1] ;
		}
		if( _req !is null ) {
			char* uri	= evhttp_request_get_uri( _req ) ;
			return fromStringz(uri);
		}
		return null ;
	}
	
	void free() {
		if( _conn !is null) {
			evhttp_connection_free(_conn);
			_conn	= null ;
			_req		= null ;
		}else if( _req !is null) {
			evhttp_request_free(_req);
			_req		= null ;
		}
	}
	
	void cancel () {
		evhttp_cancel_request(_req);
	}
	
	This timeout(int sec ){
		assert(_conn !is null);
		evhttp_connection_set_timeout(_conn, sec );
		return this;
	}
	
	This trys(int _times){
		assert(_conn !is null);
		evhttp_connection_set_retries(_conn, _times);
		return this;
	}
	
	void log(Tu...)(int ln, Tu t) {
		Thread th	= Thread.getThis ;
		Stdout.format("{}:{}:{}:`{}`	",  ln,  cast(void*) th, _req, url );
		Stdout.formatln(t).flush ;
	}
}

struct UReq{
	mixin IRequest!(UReq) parent;
	alias void delegate(This) Dg_Type ;
	Dg_Type	_dg ;
	char[]	_host ;
	int		_port ;
	int		_type = evhttp_cmd_type.EVHTTP_REQ_GET ;
	int		_stat ;
	Berkeley	_bs ;
	sockaddr	_addr ;
	
	static struct _sockaddr {
		ushort	_family ;
		ushort	_port ;
		ubyte[4]	_ip ;
	}
	
	static This Init(Http* http){
		This _this	= new UReq;
		_this.http(http);
		return _this ;
	}
	
	This url(char[] url ){
		assert(_req is null );
		assert(_conn is null );
		assert(_http !is null );

		
		if( url !is null ){
			_url	= tozString(url) ;
		}
		
		scope uri	= new Uri(_url);
		_port		= uri.port ;
		if( _port < 20 ) {
			_port	= 80 ;
		}
		_host		= tozString( uri.host );
		_conn	= evhttp_connection_base_new(_http._base, null, _host.ptr  , _port ) ;
		evhttp_connection_set_closecb(_conn, &Close_Cb, this);
		timeout(5);
		trys(3);
		
		_req	= evhttp_request_new(&Read_Cb , this) ;
		return this ;
	}
	
	
	This send() {
		assert(_req !is null );
		assert(_conn !is null );
		assert(_http !is null );
		assert(_url !is null );
		assert(_url.length > 1 );
		
		evhttp_connection_reset(_conn);
		assert( !(_conn.flags & EVHTTP_CON_INCOMING) ) ;
	
		_req.kind	= evhttp_request_kind.EVHTTP_REQUEST;
		_req.uri 	= event_mm_strdup_( _url.ptr ) ;
		_req.major	= 1 ;
		_req.minor	= 1 ;
		_req.evcon = _conn ;
		_req.type	= cast(evhttp_cmd_type) _type ;
		
		TAILQ_INSERT_TAIL!("next")(_conn.requests, _req) ;
		
		
		if (  _conn.state is 0 || _conn.state != evhttp_connection_state.EVCON_DISCONNECTED && _conn.state != evhttp_connection_state.EVCON_CONNECTING ) {
			Dns.Query( _host[0..$-1] , &onQuery );
			return this;
		}
		if( _conn.requests.tqh_first == _req ) {
			evhttp_request_dispatch(_conn);
		}
		return this;
	}
	
	void onQuery(Dns* dns) {
		if(dns._ret !is 0 ) {
			Close_Cb(_conn, this);
			return ;
		}
		
		auto bev_p	= cast(bufferevent_private*) _conn.bufev ;
		assert(bev_p !is null );
		assert(_conn.bufev !is null );
		
		_sockaddr* paddr	= cast( _sockaddr*) &_addr ;
		paddr._family	= AddressFamily.INET ;
		paddr._port		= RevertByte(cast(ushort) _port) ;
		memcpy( &paddr._ip[0], &dns._ip[0], 4 );
		
		_bs.open(AddressFamily.INET, SocketType.STREAM, ProtocolType.TCP) ;
		_bs.blocking(false);
		_conn.fd	= _bs.handle ;
		_conn.flags |= EVHTTP_CON_OUTGOING ;
	
		
		bufferevent_setfd(_conn.bufev, _conn.fd);
		bufferevent_setcb(_conn.bufev, null , null , &evhttp_connection_cb,  _conn) ;
		bufferevent_settimeout(_conn.bufev, 0,  _conn.timeout != -1 ? _conn.timeout : HTTP_CONNECT_TIMEOUT);
		bufferevent_enable(_conn.bufev, EV_WRITE);
		
		_bufferevent_incref_and_lock(_conn.bufev) ;
		bufferevent_unsuspend_write( _conn.bufev, BEV_SUSPEND_LOOKUP);
		bufferevent_unsuspend_read( _conn.bufev , BEV_SUSPEND_LOOKUP);
		
		int ret	= bufferevent_socket_connect(_conn.bufev,  &_addr , _addr.sizeof );
		assert(ret is 0);
		_bufferevent_decref_and_unlock( _conn.bufev );
		_conn.state = evhttp_connection_state.EVCON_CONNECTING ;
	
	}
	
	This dg( Dg_Type _dg){
		this._dg	= _dg ;
		return this ;
	}
	
	This free(){
		_dg	= null;
		parent.free ;
		return this ;
	}

	static extern(C) void Read_Cb(evhttp_request* req, void* ctx){
		This _this	= cast(This) ctx ;
		
		assert( !isNull( _this._dg ) );
		assert( _this._http !is null );
		assert( _this._conn !is null );
		assert( _this._req !is null );
		
		
		_this._stat	= 0 ; 
		evbuffer* ib	= evhttp_request_get_input_buffer( _this._req );
		int len	= evbuffer_get_length(ib);
		_this.log(__LINE__, "Read_Cb  len:{}" ,  len );
		evhttp_request_own( _this._req );
		_this._dg( _this ) ;
	}
	
	static extern(C) void Close_Cb(evhttp_connection* conn, void* ctx){
		This _this	= cast(This) ctx ;
		_this._stat	= 1 ; 
		if( !isNull( _this._dg) ) {
			_this.log(__LINE__, "Close_Cb call {}:{}" , _this._dg.ptr, _this._dg.funcptr );
			_this._dg( _this ) ;
		}else {
			_this.log(__LINE__, "Close_Cb sate:{} ", _this._conn.state ) ;
		}
	}
}


struct SReq{
	mixin IRequest!(SReq) parent;
	evbuffer*		_ob ;
	int			_stat ;
	Time			_now ;
	
	static This Init(Http* http, evhttp_request* req){
		This _this	= new SReq ;
		_this.http( http ) ;
		_this._req	= req ;
		_this._conn	= evhttp_request_get_connection(req) ;
		evhttp_connection_set_closecb(_this._conn, &Close_Cb, _this) ;
		
		_this._ob	= evbuffer_new() ;
		evbuffer_add_cb( _this._ob, &Write_Cb, _this);

		return _this ;
	}
	
	void service() {
		log(__LINE__, "service	type:{} ", _req.type );
		auto ureq	= UReq.Init(_http) ;
		ureq.url("http://" ~ proxy_host[0..$-1] ~  url ).dg(&onBaidu) ;
		
		evkeyvalq* lih 	= _req.input_headers ;
		auto oh		= lih.tqh_first ;
		evkeyvalq* roh 	= ureq._req.output_headers ;
		while( oh !is null ){
			char[] key	= fromStringz(oh.key) ;
			if(  key == "Host" ){
				evhttp_add_header(roh, oh.key, proxy_host.ptr );
			} else if( key == "Referer" ) {
				// Stdout(" 	")(fromStringz(oh.key))(": ")(fromStringz(oh.value))(" \n").flush ;
			} else {
				evhttp_add_header(roh, oh.key, oh.value );
			}
			// Stdout(" 	")(fromStringz(oh.key))(": ")(fromStringz(oh.value))(" \n").flush ;
			
			oh	= oh.next.tqe_next ;
		}
		
		ureq._type	= _req.type ;
		
		_now		= Clock.now ;
		log(__LINE__, " \n\n ");
		ureq.send ;
		log(__LINE__, " {}ms",  ( Clock.now  - _now ).millis );
	}
	
	void clear(){
		_http		= null ;
		_req		= null ;
		_conn	= null ;
		delete this ;
	}
	
	void onBaidu(UReq* ureq) in{
		assert(_req !is null);
		assert(ureq !is null);
		assert(ureq._req !is null);
		assert(ureq._req.input_headers !is null);
		assert(ureq._conn !is null);
	}body{
		scope(exit) {
			ureq.free ;
			clear;
		}
		
		log(__LINE__, "onBaidu {}  {}ms", ureq._stat,  ( Clock.now  - _now ).millis );
		if( ureq._stat !is 0 ){
			_stat	= 2 ; 
			const tmp1	= "error";
			evbuffer_add(_ob, tmp1.ptr , tmp1.length);
			evhttp_send_reply(_req, 200 , "Everything is fine\0".ptr , _ob ) ;
			return ;
		}
		
		evbuffer* ib = evhttp_request_get_input_buffer( ureq._req );
		int len	= evbuffer_get_length(ib);
		
		if( len is 0 ){
			const tmp1	= "error";
			evbuffer_add(_ob, tmp1.ptr , tmp1.length);
		}else{
			evkeyvalq* loh 	= _req.output_headers ;
			evkeyvalq* rih 	= ureq._req.input_headers ;
			auto ih		= rih.tqh_first ;
			while( ih !is null ){
				// Stdout(" ")(fromStringz(ih.key))(": ")(fromStringz(ih.value))(" \n").flush;
				evhttp_add_header(loh, ih.key, ih.value );
				ih	= ih.next.tqe_next ;
			}
			ubyte[1024] tmp ;
			while(true) {
				int i	= evbuffer_remove(ib, tmp.ptr, tmp.length );
				if( i is 0 || i >  tmp.length ){
					break;
				}
				evbuffer_add(_ob, tmp.ptr , i);
			}
		}
		_stat	= 1 ;
		evhttp_send_reply(_req, 200 , "Everything is fine\0".ptr , _ob ) ;
	}
	
	static extern(C) void Write_Cb(evbuffer* evb, evbuffer_cb_info* cbi , void* ctx){
		This _this	= cast(This) ctx ;
		if( _this._stat >  0  ) {
			assert( cbi.n_added is 0 );
			assert( cbi.orig_size == cbi.n_deleted);
			_this.log(__LINE__, "Write_Cb {} {}" , _this._conn ,  (cast( int*) cbi )[ 0  .. evbuffer_cb_info.sizeof / int.sizeof ] );
			// _this.free ;
		} else {
			// _this.log(__LINE__, "Write_Cb 1 {}"  ,  (cast( int*) cbi )[ 0  .. evbuffer_cb_info.sizeof / int.sizeof ] );
		}
	}
	
	static extern(C) void Close_Cb(evhttp_connection* conn, void* ctx){
		This _this	= cast(This) ctx ;
		_this.log(__LINE__, "local Close_Cb" );
		
	}
}


